package proyecto2.logica;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class NFA {

    ArrayList<Estado> estados;
    ArrayList<String> alfabeto;
    ArrayList<Transicion> transiciones;
    Estado inicial;
    ArrayList<Estado> finales;
    ArrayList<ArrayList<listObj>> matDFA;
    ArrayList<ArrayList<String>> matAux;

    public NFA() {
    }

    public NFA(ArrayList<Estado> f) {
        this.finales = f;
    }

    public NFA(ArrayList<Estado> estados, ArrayList<String> alfabeto, ArrayList<Transicion> transiciones, Estado inicial, ArrayList<Estado> finales) {
        this.estados = estados;
        this.alfabeto = alfabeto;
        this.transiciones = transiciones;
        this.inicial = inicial;
        this.finales = finales;
    }

    public ArrayList<Estado> getEstados() {
        return estados;
    }

    public void setEstados(ArrayList<Estado> estados) {
        this.estados = estados;
    }

    public ArrayList<String> getAlfabeto() {
        return alfabeto;
    }

    public void setAlfabeto(ArrayList<String> alfabeto) {
        this.alfabeto = alfabeto;
    }

    public ArrayList<Transicion> getTransiciones() {
        return transiciones;
    }

    public void setTransiciones(ArrayList<Transicion> transiciones) {
        this.transiciones = transiciones;
    }

    public Estado getInicial() {
        return inicial;
    }

    public void setInicial(Estado Inicia) {
        this.inicial = Inicia;
    }

    public ArrayList<Estado> getFinales() {
        return finales;
    }

    public void setFinales(ArrayList<Estado> finales) {
        this.finales = finales;
    }

    public boolean isDFA() {
        for (Estado es : estados) {
            for (String alfa : alfabeto) {
                if (getEstadosTransicion(es, alfa).size() > 1) {
                    return false;
                }
            }
        }
        return true;
    }

    public DFA toDFA() {
        if (isDFA()) {
            crearDfa();
            return new DFA(estados, alfabeto, transiciones, inicial, finales);
        } else {
            return crearDfa();
        }
    }

    public DFA crearDfa() {
        System.out.println("----------------------------------");
        DFA nuevo = null;
        creaMAtriz();
        System.out.println("----------------------------------");
        return nuevo;
    }

    public void dfando() {
        Estado es = inicial;
        LinkedList<Estado> stack = new LinkedList<>();
        ArrayList<Transicion> delta = new ArrayList<>();
        ArrayList<Estado> Q = new ArrayList<>();
        Q.add(es);
        stack.push(es);
        backhoe(Q, delta, stack);
    }

    public void backhoe(ArrayList<Estado> Q, ArrayList<Transicion> delta, LinkedList<Estado> stack) {
        Estado es = null;
        ArrayList<Estado> origen = new ArrayList<>();
        ArrayList<Estado> destino = new ArrayList<>();
        es = stack.pollFirst();
        String[] states = es.getId().split("-");
        for (int i = 0; i < states.length; i++) {
            origen.add(sacaXstring(estados, states[i]));
        }
        String estadoDestino;
        for (int j = 0; j < this.alfabeto.size(); j++) {
            estadoDestino = "";
            for (int i = 0; i < origen.size(); i++) {
                ArrayList<Estado> aux = getEstadosTransicion(origen.get(i), alfabeto.get(j));
                for (int k = 0; k < aux.size(); k++) {
                    estadoDestino += "-" + aux.get(k).getId();
                    System.out.println(estadoDestino);
                }
            }
        }
    }

    protected Estado sacaXstring(ArrayList<Estado> Q, String id) {
        for (int i = 0; i < Q.size(); i++) {
            if (id.equals(Q.get(i).getId())) {
                return Q.get(i);
            }
        }
        return null;
    }

    public Estado[][] creaMAtriz() {
        Estado[][] foo = new Estado[estados.size()][alfabeto.size() + 1];
        for (int i = 0; i < estados.size(); i++) {
            for (int j = 0; j < alfabeto.size(); j++) {
                //foo[i][j] = getEstadosTransicion(estados.get(i), alfabeto.get(j));
                foo[i][j] = generaCompuesto(estados.get(i), alfabeto.get(j));
            }
            foo[i][alfabeto.size()] = generaCompuesto(estados.get(i), "EPSILON");
            //foo[i][alfabeto.size()] = getEstadosTransicion(estados.get(i), "epsilon");
            //foo[i][alfabeto.size()].add(estados.get(i));
        }
        for (int i = 0; i < estados.size(); i++) {
            for (int j = 0; j < alfabeto.size() + 1; j++) {
                System.out.print(foo[i][j].getId() + " ");
            }
            System.out.println("");
        }
        return foo;
    }

    public Estado generaCompuesto(Estado e, String alfabeto) {
        String id = "";
        boolean ep = false;
        if (alfabeto.equals("EPSILON")) {
            id += e.getId();
            ep = true;
        }
        ArrayList<Estado> posibles = getEstadosTransicion(e, alfabeto);
        boolean in = false;
        boolean fn = false;
        for (int i = 0; i < posibles.size(); i++) {
            if (ep) {
                id += "-";
                ep = false;
            }
            id += posibles.get(i).getId();
            if (i < posibles.size() - 1) {
                id += "-";
            }
            if (posibles.get(i).ES_FINAL()) {
                fn = true;
            }
        }
        return new Estado(id, fn, in);

    }

    public void matDFA() {
        int n = 1;
        matDFA = new ArrayList<>();
        matAux = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            ArrayList<listObj> listaobjetos = new ArrayList<>();
            matDFA.add(listaobjetos);
            if (i == 0) {
                matAux.add(new ArrayList<String>());
                matAux.get(i).add(estados.get(i).getId());
            }
            for (int j = 0; j < alfabeto.size(); j++) {
                ArrayList<String> listE = getEstadosTransicion(matAux.get(i), alfabeto.get(j));
                for (int k = 0; k < listE.size(); k++) {
                    listObj obj = new listObj();
                    obj.setEst(getEstadosTransicion(listE.get(k), "epsilon"));
                    obj.insert(listE.get(k));
                    matDFA.get(i).add(obj);
                }
            }
            ArrayList<ArrayList<String>> nuevos = buscaElementosNuevo(matAux, matDFA.get(i));
            for (int x = 0; x < nuevos.size(); x++) {
                matAux.add(nuevos.get(x));
                n++;
            }
        }
        System.out.println(matDFA);
    }

    public ArrayList<ArrayList<String>> buscaElementosNuevo(ArrayList<ArrayList<String>> matAux, ArrayList<listObj> rowDFA) {

        ArrayList<ArrayList<String>> nuevos = new ArrayList<>();
        for (int i = 0; i < rowDFA.size(); i++) {
            String f = "";
            String a = "";
            for (int k = 0; k < rowDFA.get(i).getEst().size(); k++) {
                f = a.concat(rowDFA.get(i).getEst().get(k));
            }

            int j = 0;
            boolean band1 = true;
            while (band1 && (j < matAux.size())) {
                String h = "";
                String b = "";
                for (int x = 0; x < matAux.get(j).size(); x++) {
                    h = b.concat(matAux.get(j).get(x));
                }

                if (f.equals(h)) {
                    band1 = false;
                } else {
                    j++;
                }
            }

            if (band1) {
                nuevos.add(rowDFA.get(i).getEst());
            } else {
                System.out.println(f + "  ya exite en mat aux");
            }
        }
        return nuevos;
    }

    public void construyeAutomataDFA() {
        matDFA();
        DFA mydfa = new DFA();
        mydfa.setEstados(cargaEstadosDFA());
        mydfa.setAlfabeto(alfabeto);
        mydfa.setInicial(inicial);
        mydfa.setFinales(obtieneFinales(mydfa.getEstados()));
        mydfa.setTransiciones(cargaTransicionesDFA());
    }

    public ArrayList<Estado> obtieneFinales(ArrayList<Estado> eNuevos) {
        ArrayList<Estado> nfinales = new ArrayList<>();
        for (Estado acFinal : this.finales) {
            for (Estado nE : eNuevos) {
                if (esFinalNuevo(acFinal.getId(), nE)) {
                    nE.setES_FINAL(true);
                    nfinales.add(nE);
                }
            }
        }
        return nfinales;
    }

    public boolean esFinalNuevo(String id, Estado a) {
        String[] ids = a.getId().split("-");
        for (String id1 : ids) {
            if (id1.equals(id)) {
                return true;
            }
        }
        return false;
    }

    private ArrayList<Estado> cargaEstadosDFA() {

        ArrayList<Estado> listEstados = new ArrayList<>();

        for (int i = 0; i < matAux.size(); i++) {
            String concat = "";
            for (int j = 0; j < matAux.get(i).size(); j++) {
                concat += matAux.get(i).get(j) + ",";
            }
            listEstados.add(new Estado(concat, false, false));
        }
        return listEstados;
    }

    private ArrayList<Estado> cargaFinalesDFA() {

        ArrayList<Estado> listFinalesDFA = new ArrayList<>();

        for (int i = 0; i < matAux.size(); i++) {
            for (int j = 0; j < matAux.get(i).size(); j++) {
                for (int f = 0; f < finales.size(); f++) {
                    if (matAux.get(i).get(j).equals(finales.get(f).getId())) {
                        listFinalesDFA.add(new Estado(matAux.get(i).get(j), false, true));
                    }
                }
            }
        }
        return listFinalesDFA;
    }

    private ArrayList<Transicion> cargaTransicionesDFA() {

        ArrayList<Transicion> listTransicionesDFA = new ArrayList<>();
        ArrayList<Estado> listEst = cargaEstadosDFA();
        ArrayList<ArrayList<String>> listTran = listOfList();

        for (int i = 0; i < listEst.size(); i++) {
            for (int j = 0; j < alfabeto.size(); j++) {
                listTransicionesDFA.add(new Transicion(listEst.get(i), alfabeto.get(j), new Estado(listTran.get(i).get(j), false, false)));
            }
        }
        return listTransicionesDFA;
    }

    private ArrayList<ArrayList<String>> listOfList() {
        ArrayList<ArrayList<String>> matriz = new ArrayList<>();

        for (int i = 0; i < matDFA.size(); i++) {
            ArrayList<String> listForRow = new ArrayList<>();
            for (int j = 0; j < matDFA.get(i).size(); j++) {

                String concat = "";
                for (int x = 0; x < matDFA.get(i).get(j).getEst().size(); x++) {
                    concat += matDFA.get(i).get(j).getEst().get(x) + ",";
                }
                listForRow.add(concat);
            }
            matriz.add(listForRow);
        }
        return matriz;
    }

    public boolean recognize(String s) {
        if (isDFA()) {
            return recognizeDFA(s);
        } else {
            return this.toDFA().recognizeDFA(s);
        }
    }

    protected boolean recognizeDFA(String s) {
        Estado actual = this.getInicial();
        for (int i = 0; i < s.length(); i++) {
            if (perteneceAlfabeto(s.charAt(i))) {
                actual = buscarEstado(actual, String.valueOf(s.charAt(i)));
            } else {
                return false;
            }
        }
        System.err.println("actual->" + actual.toString());
        return actual.ES_FINAL();
    }

    private boolean perteneceAlfabeto(char caracter) {
        for (String al : alfabeto) {
            if (String.valueOf(caracter).equals(al)) {
                return true;
            }
        }
        return false;
    }

    private Estado buscarEstado(Estado actual, String subhilera) {
        Estado siguiente = null;
        for (Transicion t : this.getTransiciones()) {
            if (t.getOrigen().equals(actual) && t.getVariable().equals(subhilera)) {
                siguiente = t.getDestino();
            }
        }
        return siguiente;
    }

    protected boolean recognizeNFA(String s) {
        return this.toDFA().recognizeDFA(s);
    }

//    private boolean recognizeNFA(String s) {
//        ArrayList<Estado> actuales = new ArrayList<>();
//        LinkedList<Estado> stack = new LinkedList<>();
//        stack.add(this.inicial);
//        int i = 0;
//        int tamano = s.length();
//        return backcrapeando(s, stack, actuales, i, tamano);
//    }
    private boolean backcrapeando(String s, LinkedList<Estado> stack, ArrayList<Estado> actuales, int i, int tamano) {
        Estado es = null;
        if (tamano > i) {
            es = stack.pollFirst();
            if (es != null) {
                actuales = null;
                actuales = getEstadosTransicion(es, s.charAt(i) + "");
                if (actuales != null) {
                    for (int j = 0; j < actuales.size(); j++) {
                        stack.push(actuales.get(j));
                    }
                }
                backcrapeando(s, stack, actuales, ++i, tamano);
            }
        }
        actuales = null;
        actuales = new ArrayList<>();
        boolean bool = false;
        while (!stack.isEmpty()) {
            actuales.add(stack.pollFirst());
        }
        for (int j = 0; j < actuales.size(); j++) {
            if (actuales.get(j).ES_FINAL()) {
                System.out.println("s:" + j);
                bool = true;
                break;
            }
        }
        System.out.println("bool:" + bool);
        return bool;
    }

    public ArrayList<Estado> getEstadosTransicion(Estado actual, String alfabeto) {
        ArrayList<Estado> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            if (tr.getOrigen().getId().equals(actual.getId()) && tr.getVariable().equals(alfabeto)) {
                posibles.add(tr.getDestino());
            }
        }
        posibles.addAll(getEstadosEpsilon(actual));
        return posibles;
    }

    public ArrayList<Estado> getEstadosEpsilon(Estado actual) {
        ArrayList<Estado> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            if (tr.getOrigen().getId().equals(actual.getId()) && tr.getVariable().equals("EPSILON")) {
                posibles.add(tr.getDestino());
            }
        }
        return posibles;
    }

    public ArrayList<String> getEstadosTransicion(ArrayList<String> actual, String alfabeto) {

        ArrayList<String> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            for (int i = 0; i < actual.size(); i++) {
                if (tr.getOrigen().getId().equals(actual.get(i)) && tr.getVariable().equals(alfabeto)) {
                    posibles.add(tr.getDestino().getId());
                }
            }
        }
        return posibles;
    }

    public ArrayList<String> getEstadosTransicion(String actual, String alfabeto) {

        ArrayList<String> posibles = new ArrayList<>();
        for (Transicion tr : transiciones) {
            if (tr.getOrigen().getId().equals(actual) && tr.getVariable().equals(alfabeto)) {
                posibles.add(tr.getDestino().getId());
            }

        }
        return posibles;
    }

    @Override
    public String toString() {
        return "Automaton{" + "estados=" + estados + ", \nalfabeto=" + alfabeto + ", \ntransiciones=" + transiciones + ", inicial=" + inicial + ", finales=" + finales + '}';
    }

}
